package project.environments;

import project.parser.ast.aux.Function;
import project.parser.ast.exps.Ident;

import java.util.HashMap;
import java.util.Map;
import java.util.Stack;

public class EnvironmentClass<T, U extends T> implements Environment<T,U> {

	protected Map<Ident,U> funcsEnv;
	protected Stack<Map<Ident,T>> localEnvs;

	public EnvironmentClass() { //il costruttore aggiunge lo scope globale
		funcsEnv = new HashMap<>();
		localEnvs = new Stack<>();
		localEnvs.push(new HashMap<>());
	}

	@Override
	public void enterScope() {
		localEnvs.push(new HashMap<Ident,T>());
	}

	@Override
	public void exitScope() {
		localEnvs.pop();
	}

	@Override
	public void create(Ident id, T value) {
		Map<Ident,T> locEnv = localEnvs.peek();
		if (locEnv.get(id) != null)
			throw new EnvironmentException("Identifier '" + id + "' already declared");
		locEnv.put(id, value);
	}

	@Override
	public void funcCreate(Ident id, U value) {
		if (!(value instanceof Function))
			throw new EnvironmentException("'" + id + "' is not a function");
		if (funcsEnv.get(id) != null)
			throw new EnvironmentException("Function '" + id + "' already declared");
		funcsEnv.put(id, value);
	}

	@Override
	public void update(Ident id, T value) {
		Map<Ident,T> locEnv;
		for (int i = localEnvs.size(); i > 0; ) {
			locEnv = localEnvs.get(--i);
			if (locEnv.get(id) != null) {
				locEnv.put(id, value);
				return;
			}
		}
		throw new EnvironmentException("Undeclared identifier '" + id + "'");
	}

	@Override
	public T lookup(Ident id) {
		Map<Ident,T> localEnv;
		T value;
		for (int i = localEnvs.size(); i > 0; ) {
			localEnv = localEnvs.get(--i);
			value = localEnv.get(id);
			if (value != null)
				return value;
		}
		throw new EnvironmentException("Undeclared identifier '" + id + "'");
	}

	@Override
	public U funcLookup(Ident id) {
		U result = funcsEnv.get(id);
		if (result == null)
			throw new EnvironmentException("Undeclared function '" + id + "'");
		return result;
	}
}
